package com.xp.tinyurl.common.utils;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * LEi = REi-1 （1）
 * REi=LEi-1⊕F(REi-1, Ki) （2）
 * 明文分组被分为等长的两部分：L0和R0，这两部分数据经过n轮迭代后合并成密文组。
 * 第i轮迭代的输入Li-1和Ri-1来自于上轮迭代的输出；
 * 而输入的子密钥Ki是由整个密钥K推导出来的
 *
 * @author  (xp)
 **/
public class FeistelCipher {
    private static final Logger LOGGER = LoggerFactory.getLogger(FeistelCipher.class);
    /**
     * Feistel 运算的轮数
     */
    private static final int ROUND_COUNT = 3;
    private static final int BIT_SIZE = Integer.SIZE;

    /**
     * 我们只用到了Feistel的排列
     */
    public static long permutedId(long input) {
        long output = doPermutedId(input);
        if (output < 0) {
            LOGGER.warn("output:{} less than zero for input:{}", output, input);
        }
        return output;
    }

    /**
     * 逆排列
     */
    public static long inversePermutedId(long input) {
        return doPermutedId(input);
    }

    private static long doPermutedId(long input) {
        // Split the result into left and right halves.
        long left = input >>> BIT_SIZE;
        long right = input & 0xFFFFFFFFL;

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("leftCurrent:{}, rightCurrent:{}", toBinary(left), toBinary(right));
        }

        // Run Feistel network algorithm.
        for (int i = 0; i < ROUND_COUNT; i++) {
            long tempRight = right;
            right = left ^ fFunction(right);
            left = tempRight;

            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("leftCurrent:{}, rightCurrent:{}", toBinary(left), toBinary(right));
            }
        }
        return (right << BIT_SIZE) | left;
    }

    /**
     * 自定义的F函数。
     * 对于Feistel网络结构，其加密核心在与F函数的选定，不同的F函数就是遵循Feistel结构的不同的加密算法（如DES），一个好的F函数对于加密效果至关重要，一般情况下，F函数需要满足以下几点：
     * ①不要求可逆：即不求F函数有反函数
     * ②非线性；
     * ③混乱性；
     * ④扩散性；
     * ⑤雪崩性：即随着轮数增加其加密效果雪崩式增强；
     */
    private static long fFunction(long right) {
        return (long) ((((1366.0 * right + 150819) % 714025) / 714025.0) * 1023);
    }

    private static String toBinary(long leftPrevious) {
        return StringUtils.leftPad(Long.toBinaryString(leftPrevious), Long.SIZE / 2, "0");
    }
}
